﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Shapes;
using System.Windows.Threading;
using TextEditor.TextBoxInner.Layers;
using TextEditor.TextBoxInner.Utils;
using TextEditor.Utils;
using UniversalEditor.Base;
using UniversalEditor.Base.Mvvm;
using UniversalEditor.Base.Options;

namespace TextEditor.TextBoxInner
{
	public class UfeTextBox2 : FrameworkElement, IScrollInfo
	{
		private readonly IEditorOwner _owner;
		private readonly UfeTextDocument _document;

		private readonly Brush _backgroungBrush = Brushes.White;
		private readonly VisualCollection _layers;

		private readonly TextLayer _textLayer = new TextLayer();
		private readonly NumberLayer _numbersLayer = new NumberLayer();
		private readonly CollapseLayer _collapseLayer = new CollapseLayer();
		private readonly SelectionLayer _selectionLayer = new SelectionLayer();

		private readonly ActionStackExplorer _actionStackExplorer;

		private readonly SimpleCommand _copyCommand;
		private readonly SimpleCommand _cutCommand;
		private readonly SimpleCommand _pasteCommand;
		private readonly SimpleCommand _deleteCommand;
		private readonly SimpleCommand _commentSelectedLinesCommand;
		private readonly SimpleCommand _formattingCommand;
		private readonly SimpleCommand _commentSelectedTextCommand;

//		private readonly UfePopup _popup;

		private DateTime _lastMouseDownEvent = DateTime.MinValue;
		private CharPosition _lastMouseDownPosition;
		private bool _isLastMouseDownDouble = false;

		private bool _canVerticallyScroll = true;
		private bool _canHorizontallyScroll = true;

		private bool _showNumbers = Options.Instance.ShowLineNumbers;
		private bool _isReadonly = false;
		
		private Size _viewport;
		private Size _extent;
		private Vector _offset;

		private object _longestLineHandle;
		private double _longestLineWidth = -1;

		private CharPosition _startSelection = new CharPosition(0, 0);
		private CharPosition _endSelection = new CharPosition(0, 0);

		public UfeTextDocument Document
		{
			get { return _document; }
		}
		
		public UfeTextBox2(IEditorOwner owner, UfeTextDocument document)
		{
			if (owner == null) 
				throw new ArgumentNullException("owner");
			if (document == null)
				throw new ArgumentNullException("document");

			_owner = owner;
			_document = document;

			_actionStackExplorer = new ActionStackExplorer(this);

			_copyCommand = new SimpleCommand(OnCopy, IsCopyEnable);
			_cutCommand = new SimpleCommand(OnCut, IsCutEnable);
			_pasteCommand = new SimpleCommand(OnPaste, () => !IsReadonly);
			_deleteCommand = new SimpleCommand(OnRemoveSelection, IsCutEnable);
			_commentSelectedLinesCommand = new SimpleCommand(CommentSelectedLines, () => !IsReadonly);
			_formattingCommand = new SimpleCommand(FormatSelectedLines, () => !IsReadonly);
			_commentSelectedTextCommand = new SimpleCommand(CommentSelectedText, () => !IsReadonly);

			HorizontalAlignment = HorizontalAlignment.Left;
			VerticalAlignment = VerticalAlignment.Top;

			_layers = new VisualCollection(this);

			_layers.Add(new Rectangle() { MinWidth = 100, MinHeight = 100, Fill = Brushes.Aqua});
			_layers.Add(_selectionLayer);
			_layers.Add(_numbersLayer);
			_layers.Add(_textLayer);
			_layers.Add(_collapseLayer);
			

			if (!_showNumbers)
			{
				_numbersLayer.Visibility = Visibility.Collapsed;
				_collapseLayer.Visibility = Visibility.Collapsed;
			}

			UpdateExtendSize();
		}

		private void FormatSelectedLines()
		{
			int startRow = Math.Min(_startSelection.Row, _endSelection.Row);
			int endRow = Math.Max(_startSelection.Row, _endSelection.Row);

			FormatLines(startRow, endRow);
			_document.SetIsChangedToTrue();
			InvalidateVisual();
		}

		protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
		{
			OnMouseButtonDown(e);
		}

		protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e)
		{
			OnMouseButtonUp(e);
		}

		private void OnMouseButtonDown(MouseButtonEventArgs e)
		{
			Point location = e.GetPosition(this);
			CharPosition temp = _textLayer.GetChartPosition(location);

//			if (IsCollapsePlace(location))
//			{
//				_document.CollapseSwitch(temp.Row);
//				InvalidateVisual();
//				return;
//			}

//			if (Keyboard.Modifiers == ModifierKeys.Control)
//			{
//				bool success = TryOpenFileUnderCursor(temp);
//
//				if (success)
//					return;
//			}

			if (DateTime.UtcNow - _lastMouseDownEvent > TimeSpan.FromMilliseconds(300))
			{
				_isLastMouseDownDouble = false;

				if (!Keyboard.IsKeyDown(Key.LeftShift))
				{
					if (temp != null)
						SetPosition(temp);

					_lastMouseDownPosition = temp;
				}
			}
			else
			{
				_isLastMouseDownDouble = true;
				_lastMouseDownPosition = temp;

				if (temp != null)
				{
					string line = _document.GetLineText(temp.Row);
					int start = Math.Max(line.LastIndexOfAny(UfeTextBoxInner.CtrlSplitChars, Math.Min(temp.Col, line.Length - 1)) + 1, 0);
					int end = line.IndexOfAny(UfeTextBoxInner.CtrlSplitChars, temp.Col);

					if (end < 0)
						end = line.Length;

					SetPosition(new CharPosition(start, temp.Row), new CharPosition(end, temp.Row), true);
				}
			}

			_lastMouseDownEvent = DateTime.UtcNow;

			e.Handled = true;
			Keyboard.Focus(this);
			CaptureMouse();
		}

		private void OnMouseButtonUp(MouseButtonEventArgs e)
		{
			Point location = e.GetPosition(this);

//			if (IsCollapsePlace(location))
//				return;

			if (_isLastMouseDownDouble)
			{
				/*Point location = e.GetPosition(this);
				CharPosition temp = GetChartPosition(location);

				if (temp != null)
				{
					UfeTextLineBase line = _document.GetLine(temp.Row);
//					int start = Math.Max(line.TextAsString.LastIndexOfAny(CtrlSplitChars, Math.Min(temp.Col, line.TextAsString.Length - 1)) + 1, 0);
					int end = line.TextAsString.IndexOfAny(CtrlSplitChars, temp.Col);

					if (end < 0)
						end = line.TextAsString.Length;

					SetPosition(null, new CharPosition(end, temp.Row));
				}*/
			}
			else
			{
				CharPosition temp = _textLayer.GetChartPosition(location);

				if (temp != null)
					SetPosition(null, temp, true);
			}

			e.Handled = true;
			Keyboard.Focus(this);
			ReleaseMouseCapture();
		}

		/*private CharPosition GetChartPosition(Point location)
		{
			for (int index = 0; index < _linesGeometry.Count; index++)
			{
				UfeTextBoxInner.TextPosition text = _linesGeometry[index];

				if (text == null || text.Top > location.Y || text.Buttom < location.Y)
					continue;

				if (string.IsNullOrEmpty(text.TextAsString.Trim()))
					return new CharPosition(text.TextAsString.Length, text.Line);

				double x = _textLayer.GetLeft() - 2;
				int chCount = 0;
				for (int col = 0; col < text.TextAsString.Length; col++)
				{
					char ch = text.TextAsString[col];

					if (ch == '\t')
					{
						chCount += 4 - chCount % 4;
					}
					else
					{
						chCount++;
					}

					double newX = _textLayer.GetLeft() + chCount * fontWidth - 2;

					if (location.X <= (x + newX) / 2d)
						return new CharPosition(col, text.Line);
					if ((x + newX) / 2d < location.X && location.X <= newX)
						return new CharPosition(col + 1, text.Line);

					x = newX;
				}

				return new CharPosition(text.TextAsString.Length, text.Line);
			}

			if (_textLayer. _linesGeometry.Count > 0)
				return new CharPosition(_linesGeometry.Last().TextAsString.Length, _linesGeometry.Last().Line);

			return null;
		}*/

		private void CommentSelectedText()
		{
			if (!IsReadonly)
			{
				CharPosition maxPosition = _startSelection > _endSelection ? _startSelection : _endSelection;
				CharPosition minPosition = _startSelection < _endSelection ? _startSelection : _endSelection;

				IList<TextLineChangesActionBase> info = _document.CommentText(minPosition, maxPosition);

				if (info != null && info.Count > 0)
				{
					try
					{
						_actionStackExplorer.BeginMacro();

						int start = minPosition.Col;
						int end = maxPosition.Col;

						_document.ExpandeIfNeed(minPosition.Row, maxPosition.Row);

						for (int index = 0; index < info.Count; index++)
						{
							TextLineChangesActionBase target = info[index];
							MakeTextChangesAction(target, minPosition.Row, ref start, maxPosition.Row, ref end);
						}

						SetPosition(new CharPosition(start, minPosition.Row), new CharPosition(end, maxPosition.Row), true);
					}
					finally
					{
						_actionStackExplorer.EndMacro();
					}

					InvalidateVisual();
					_document.SetIsChangedToTrue();
				}
			}
		}

		public void Remove(int row, int colStart, int length)
		{
			Remove(new CharPosition(colStart, row), new CharPosition(colStart + length, row));
		}

		private void OnPaste()
		{
			try
			{
				_actionStackExplorer.BeginMacro();

				string text = Clipboard.GetText();
				string[] lines = text.Split(new[] { Environment.NewLine }, StringSplitOptions.None);

				OnRemoveSelection();

				if (lines.Length > 0)
				{
					for (int index = 0, col = _startSelection.Col; index < lines.Length; index++, col = 0)
					{
						//						string line = _document.GetLineText(_startSelection.Row + index);
						string pasteText = lines[index];

						Insert(_startSelection.Row + index, col, pasteText);
						string line = _document.GetLineText(_startSelection.Row + index);

						if (index != lines.Length - 1)
						{
							string newLine = line.Substring(col + pasteText.Length);
							InsertLine(_startSelection.Row + index + 1, newLine, false);
							Remove(_startSelection.Row + index, line.Length - newLine.Length, newLine.Length);
						}
						else
						{
							SetPosition(new CharPosition(col + pasteText.Length, _startSelection.Row + index));
						}
					}
				}

				_document.SetIsChangedToTrue();

				_longestLineWidth = -1;
				FindTheLongestLine();
			}
			finally
			{
				_actionStackExplorer.EndMacro();
			}
		}

		private void FindTheLongestLine()
		{
			for (int index = 0; index < _document.LineCount; index++)
			{
				double lineWidth = _document.GetLineWidthInPixels(index);

				if (_longestLineWidth < lineWidth)
				{
					_longestLineHandle = _document.GetLineHandle(index);
					_longestLineWidth = lineWidth;
				}
			}
		}

		private void FormatLines(int startRow, int endRow)
		{
			try
			{
				_document.ExpandeIfNeed(startRow, endRow);

				_actionStackExplorer.BeginMacro();

				string[] rows = _document.GetFormattedLines(startRow, endRow);

				if (rows.Length == 0)
					return;

				SetPosition(new CharPosition(0, startRow), new CharPosition(rows.Last().Length, startRow + rows.Length - 1), true);

				for (int index = endRow; index >= startRow; index--)
					RemoveRow(index);

				for (int index = rows.Length - 1; index >= 0; index--)
					InsertLine(startRow, rows[index], false);

				SetPosition(new CharPosition(0, startRow), new CharPosition(rows.Last().Length, startRow + rows.Length - 1), true);
			}
			finally
			{
				_actionStackExplorer.EndMacro();
			}
		}

		public int InsertLine(int index, string text, bool addTabsToStart)
		{
			StringBuilder prefix = new StringBuilder();

			if (addTabsToStart && index > 0)
			{
				string prevText = _document.GetLineText(index - 1);

				for (int marker = 0; marker < prevText.Length; marker++)
				{
					char ch = prevText[marker];

					if (ch == ' ' || ch == '\t')
						prefix.Append(ch);
					else break;
				}
			}

			SingleActionPair newLine = new SingleActionPair(
				new UfeTextBoxInner.AddLineAction(index),
				new UfeTextBoxInner.RemoveLineAction(index));
			_actionStackExplorer.DoAction(newLine);

			SingleActionPair addText = new SingleActionPair(
				new SetTextAction(index, prefix + text),
				new SetTextAction(index, string.Empty));
			_actionStackExplorer.DoAction(addText);

			return prefix.Length;
		}

		internal void RemoveRow(int index)
		{
			string line = _document.GetLineText(index);

			SingleActionPair singleActionPair = new SingleActionPair(
					new SetTextAction(index, string.Empty),
					new SetTextAction(index, line));
			_actionStackExplorer.DoAction(singleActionPair);

			SingleActionPair pair = new SingleActionPair(
						new UfeTextBoxInner.RemoveLineAction(index),
						new UfeTextBoxInner.AddLineAction(index));
			_actionStackExplorer.DoAction(pair);
		}

		private void CommentSelectedLines()
		{
			if (!IsReadonly)
			{
				CharPosition maxPosition = _startSelection > _endSelection ? _startSelection : _endSelection;
				CharPosition minPosition = _startSelection < _endSelection ? _startSelection : _endSelection;

				try
				{
					_actionStackExplorer.BeginMacro();

					int startRow = minPosition.Row;
					int endRow = maxPosition.Row;

					int startCol = minPosition.Col;
					int endCol = maxPosition.Col;

					_document.ExpandeIfNeed(startRow, endRow);

					IList<TextLineChangesActionBase> info = _document.CommentLines(startRow, endRow);

					if (info != null && info.Count > 0)
					{
						for (int marker = 0; marker < info.Count; marker++)
						{
							TextLineChangesActionBase item = info[marker];
							MakeTextChangesAction(item, startRow, ref startCol, endRow, ref endCol);
						}
					}

					SetPosition(new CharPosition(startCol, startRow), new CharPosition(endCol, endRow), true);
				}
				finally
				{
					_actionStackExplorer.EndMacro();
				}

				InvalidateVisual();
				_document.SetIsChangedToTrue();

			}
		}

		public void Insert(int row, int col, string text)
		{
			string line = _document.GetLineText(row);

			SingleActionPair pair = new SingleActionPair(
						new SetTextAction(row, line.Insert(col, text)),
						new SetTextAction(row, line));
			_actionStackExplorer.DoAction(pair);
		}

		private void MakeTextChangesAction(TextLineChangesActionBase action, int startRow, ref int startCol, int endRow, ref int endCol)
		{
			if (action is TextLineInsertAction)
			{
				TextLineInsertAction item = (TextLineInsertAction)action;
				Insert(item.Position.Row, item.Position.Col, item.Text);

				if (item.Position.Row == startRow && item.Position.Col < startCol)
					startCol += item.Text.Length;
				if (item.Position.Row == endRow && item.Position.Col <= endCol)
					endCol += item.Text.Length;
			}
			else if (action is TextLineRemoveAction)
			{
				TextLineRemoveAction item = (TextLineRemoveAction)action;
				Remove(item.Position, new CharPosition(item.Position.Col + item.TextLength, item.Position.Row));

				// TODO: make it smarter
				if (item.Position.Row == startRow && item.Position.Col < startCol)
					startCol -= item.TextLength;
				if (item.Position.Row == endRow && item.Position.Col <= endCol)
					endCol -= item.TextLength;
			}
			else
			{
				Debug.Fail("Here's unsupported action.");
			}
		}

		private void OnRemoveSelection()
		{
			Remove(_startSelection, _endSelection);

			int newCol = _startSelection < _endSelection ? _startSelection.Col : _endSelection.Col;
			int newRow = _startSelection < _endSelection ? _startSelection.Row : _endSelection.Row;

			SetPosition(new CharPosition(newCol, newRow));
			_document.SetIsChangedToTrue();
		}

		private void SetPosition(CharPosition position)
		{
			SetPosition(position, position, true);
		}

		public void Remove(CharPosition startSelection, CharPosition endSelection)
		{
			if (startSelection == endSelection)
				return;

			CharPosition begin = startSelection < endSelection ? startSelection : endSelection;
			CharPosition end = begin == startSelection ? endSelection : startSelection;

			if (begin.Row == end.Row)
			{
				string line = _document.GetLineText(begin.Row);
				string nextText = line.Substring(0, begin.Col) + line.Substring(end.Col, line.Length - end.Col);

				SingleActionPair pair = new SingleActionPair(
					new SetTextAction(begin.Row, nextText),
					new SetTextAction(begin.Row, line));
				_actionStackExplorer.DoAction(pair);
			}
			else
			{
				for (int index = end.Row - 1; index > begin.Row; index--)
				{
					SingleActionPair action = new SingleActionPair(
						new SetTextAction(index, string.Empty),
						new SetTextAction(index, _document.GetLineText(index)));
					_actionStackExplorer.DoAction(action);

					SingleActionPair pair = new SingleActionPair(
						new UfeTextBoxInner.RemoveLineAction(index),
						new UfeTextBoxInner.AddLineAction(index));
					_actionStackExplorer.DoAction(pair);
				}

				string lastPart = _document.GetLineText(begin.Row + 1).Substring(end.Col);

				SingleActionPair action1 = new SingleActionPair(
						new SetTextAction(begin.Row + 1, string.Empty),
						new SetTextAction(begin.Row + 1, _document.GetLineText(begin.Row + 1)));
				_actionStackExplorer.DoAction(action1);

				SingleActionPair actionPair = new SingleActionPair(
					new UfeTextBoxInner.RemoveLineAction(begin.Row + 1),
					new UfeTextBoxInner.AddLineAction(begin.Row + 1));
				_actionStackExplorer.DoAction(actionPair);

				string line = _document.GetLineText(begin.Row);
				string nextText = line.Substring(0, begin.Col) + lastPart;

				SingleActionPair singleActionPair = new SingleActionPair(
					new SetTextAction(begin.Row, nextText),
					new SetTextAction(begin.Row, line));
				_actionStackExplorer.DoAction(singleActionPair);
			}
		}

		private void OnCut()
		{
			OnCopy();
			OnRemoveSelection();
			_document.SetIsChangedToTrue();
		}

		public bool IsReadonly
		{
			get { return _isReadonly; }
			set
			{
				if (_isReadonly == value)
					return;

				_isReadonly = value;

				_commentSelectedLinesCommand.RiseCanExecuteChanged();
				_commentSelectedTextCommand.RiseCanExecuteChanged();
				_pasteCommand.RiseCanExecuteChanged();
				_cutCommand.RiseCanExecuteChanged();
				InvalidateVisual();
			}
		}

		private void OnCopy()
		{
			DataObject data = new DataObject();

//			CopyAsText(data);
//			CopyAsRtf(data);

			Clipboard.SetDataObject(data, true);
		}

		private bool IsCopyEnable()
		{
			return _startSelection != _endSelection;
		}

		private bool IsCutEnable()
		{
			return !IsReadonly && IsCopyEnable();
		}

		private void UpdateExtendSize()
		{
			double longestLineWidth = 0;

			for (int index = 0; index < _document.LineCount; index++)
			{
				double lineWidth = _document.GetLineWidthInPixels(index);

				if (longestLineWidth < lineWidth)
					longestLineWidth = lineWidth;
			}

			_extent = new Size(longestLineWidth, _document.VisibleLineCount * FontHelper.FontHeight);
		}

		protected override Size ArrangeOverride(Size finalSize)
		{
			UpdateLayers();
			return _extent;
		}

		protected override void OnRender(DrawingContext drawingContext)
		{
			drawingContext.DrawRectangle(_backgroungBrush, null, new Rect(_viewport));
			base.OnRender(drawingContext);
		}

		private void UpdateLayers()
		{
			int startLineIndex = Convert.ToInt32(_offset.Y/FontHelper.FontHeight);
			int linesCount = _document.LineCount;

			if (startLineIndex >= linesCount)
			{
				_textLayer.SetLines(new RenderedInfo[0]);
				_numbersLayer.SetLines(new int[0]);
			}
			else
			{
				int endLineIndex = Convert.ToInt32((_offset.Y + _viewport.Height) / FontHelper.FontHeight);
				int end = Math.Min(endLineIndex, linesCount - 1);

				double textLayerOffset = 2;

				if (_showNumbers)
				{
					_numbersLayer.SetLines(Enumerable.Range(startLineIndex, end - startLineIndex + 1).ToArray());

					double collapseLayerOffset = end.ToString(CultureInfo.InvariantCulture).Length * FontHelper.FontWidth + 2;
					_collapseLayer.SetLeft(collapseLayerOffset);

					textLayerOffset = collapseLayerOffset + 2;
				}
 
				_textLayer.SetLeft(textLayerOffset);
				_textLayer.SetLines(Enumerable.Range(startLineIndex, end - startLineIndex + 1).Select(x => _document.GetRenderedInfo(x)).ToArray());
			}

			UpdateSelection();
		}

		private void UpdateSelection()
		{
			int startLineIndex = Convert.ToInt32(_offset.Y / FontHelper.FontHeight);
			int linesCount = _document.LineCount;

			if (startLineIndex >= linesCount)
			{
				_selectionLayer.SetLines(new SelectionLineInfo[0]);
			}
			else
			{
				int endLineIndex = Convert.ToInt32((_offset.Y + _viewport.Height) / FontHelper.FontHeight);
				int end = Math.Min(endLineIndex, linesCount - 1);

				double textLayerOffset = 2;

				if (_showNumbers)
				{
					double collapseLayerOffset = end.ToString(CultureInfo.InvariantCulture).Length * FontHelper.FontWidth + 2;
					textLayerOffset = collapseLayerOffset + 2;
				}

				CharPosition startPosition = _startSelection < _endSelection ? _startSelection : _endSelection;
				CharPosition endPosition = _startSelection < _endSelection ? _endSelection : _startSelection;

				_selectionLayer.SetLeft(textLayerOffset);

				bool isSingleRowSelection = startPosition.Row == endPosition.Row;
				var selectionLines = from lineIndex in Enumerable.Range(startLineIndex, end - startLineIndex + 1)
									 let isEmpty = lineIndex < startPosition.Row || lineIndex > endPosition.Row
									 select isEmpty ? SelectionLineInfo.CreateEmptySelection(lineIndex) :
											isSingleRowSelection ? new SelectionLineInfo(lineIndex, startPosition.Col, endPosition.Col) :
											lineIndex == startPosition.Row ? new SelectionLineInfo(lineIndex, startPosition.Col, _document.GetLineLength(lineIndex)) :
											lineIndex == endPosition.Row ? new SelectionLineInfo(lineIndex, 0, endPosition.Col) :
																			new SelectionLineInfo(lineIndex, 0, _document.GetLineLength(lineIndex));

				_selectionLayer.SetLines(selectionLines.ToArray());
			}
		}

		protected void VerifyScrollData()
		{
			if (double.IsInfinity(_viewport.Width))
				_viewport.Width = _extent.Width;

			if (double.IsInfinity(_viewport.Height))
				_viewport.Height = SystemParameters.PrimaryScreenHeight;

			_offset.X = Math.Max(0, Math.Min(_offset.X, _extent.Width - _viewport.Width));
			_offset.Y = Math.Max(0, Math.Min(_offset.Y, _extent.Height - _viewport.Height));

			if (ScrollOwner != null)
				ScrollOwner.InvalidateScrollInfo();
		}

		protected override Visual GetVisualChild(int index)
		{
			return _layers[index];
		}

		protected override int VisualChildrenCount
		{
			get { return _layers.Count; }
		}
		
		protected override Size MeasureOverride(Size availableSize)
		{
			_viewport = availableSize;

			VerifyScrollData();
			UpdateVisibleLinesCount();

			if (ScrollOwner != null)
				ScrollOwner.InvalidateScrollInfo();

			return _viewport;
		}

		private void UpdateVisibleLinesCount()
		{
			int linesCount = Convert.ToInt32(_viewport.Height/FontHelper.FontHeight) + 2;

			_textLayer.SetLinesCount(linesCount);
			_selectionLayer.SetLinesCount(linesCount);
			_numbersLayer.SetLinesCount(linesCount);
		}

		public void LineUp()
		{
			SetScrollOffset(_offset.X, _offset.Y - FontHelper.FontHeight);
		}

		public void LineDown()
		{
			SetScrollOffset(_offset.X, _offset.Y + FontHelper.FontHeight);
		}

		public void LineLeft()
		{
			SetScrollOffset(_offset.X - FontHelper.FontWidth, _offset.Y);
		}

		public void LineRight()
		{
			SetScrollOffset(_offset.X + FontHelper.FontWidth, _offset.Y);
		}

		public void PageUp()
		{
		}

		public void PageDown()
		{
		}

		public void PageLeft()
		{
		}

		public void PageRight()
		{
		}

		public void MouseWheelUp()
		{
			SetScrollOffset(_offset.X, _offset.Y - 3 * FontHelper.FontHeight);
		}

		public void MouseWheelDown()
		{
			SetScrollOffset(_offset.X, _offset.Y + 3 * FontHelper.FontHeight);
		}

		public void MouseWheelLeft()
		{
			SetScrollOffset(_offset.X - 3 * FontHelper.FontWidth, _offset.Y);
		}

		public void MouseWheelRight()
		{
			SetScrollOffset(_offset.X + 3 * FontHelper.FontWidth, _offset.Y);
		}

		public void SetHorizontalOffset(double offset)
		{
			SetScrollOffset(offset, _offset.Y);
		}

		public void SetVerticalOffset(double offset)
		{
			SetScrollOffset(_offset.X, offset);
		}

		private void SetScrollOffset(double x, double y)
		{
			if (x > _extent.Width - _viewport.Width) 
				x = _extent.Width - _viewport.Width;
			if (x < 0)
				x = 0;
			if (y > _extent.Height - _viewport.Height) 
				y = _extent.Height - _viewport.Height;
			if (y < 0)
				y = 0;

			if (Math.Abs(_offset.X - x) >= 10 || Math.Abs(_offset.Y - y) >= 10)
			{
				_offset.X = x;
				_offset.Y = y;

				if (ScrollOwner != null)
					ScrollOwner.InvalidateScrollInfo();

				UpdateLayers();
			}
		}

		public Rect MakeVisible(Visual visual, Rect rectangle)
		{
			return rectangle;
		}

		public bool CanVerticallyScroll
		{
			get { return _canVerticallyScroll; }
			set { _canVerticallyScroll = value; }
		}

		public bool CanHorizontallyScroll
		{
			get { return _canHorizontallyScroll; }
			set { _canHorizontallyScroll = value; }
		}

		public double ExtentWidth
		{
			get { return _extent.Width; }
		}

		public double ExtentHeight
		{
			get { return _extent.Height; }
		}

		public double ViewportWidth 
		{
			get { return _viewport.Width; }
		}

		public double ViewportHeight
		{
			get { return _viewport.Height; }
		}

		public double HorizontalOffset
		{
			get { return _offset.X; }
		}

		public double VerticalOffset
		{
			get { return _offset.Y; }
		}

		public ScrollViewer ScrollOwner { get; set; }

		internal void SetPosition(CharPosition start, CharPosition end, bool registerInActionStack)
		{
			if (start != null)
			{
				if (start.Row >= _document.LineCount)
					start = new CharPosition(start.Col, _document.LineCount - 1);
				if (start.Row < 0)
					start = new CharPosition(start.Col, 0);

				int lineLength = _document.GetLineLength(start.Row);
				if (start.Col > lineLength)
					start = new CharPosition(lineLength, start.Row);
				if (start.Col < 0)
					start = new CharPosition(0, start.Row);
			}

			if (end != null)
			{
				if (end.Row >= _document.LineCount)
					end = new CharPosition(end.Col, _document.LineCount - 1);
				if (end.Row < 0)
					end = new CharPosition(end.Col, 0);

				int lineLength = _document.GetLineLength(end.Row);
				if (end.Col > lineLength)
					end = new CharPosition(lineLength, end.Row);
				if (end.Col < 0)
					end = new CharPosition(0, end.Row);
			}

			if (!registerInActionStack)
			{
				_startSelection = start;
				_endSelection = end;

				UpdateSelection();
				HideHint();

				_copyCommand.RiseCanExecuteChanged();
				_cutCommand.RiseCanExecuteChanged();
				_deleteCommand.RiseCanExecuteChanged();

				return;
			}

			if (start != null || end != null)
				AddPositionChangedEvent(start ?? _startSelection, end ?? _endSelection, _startSelection, _endSelection);
		}

		protected override void OnMouseMove(MouseEventArgs e)
		{
			Point location = e.GetPosition(this);

			if (e.LeftButton == MouseButtonState.Pressed /*|| e.RightButton == MouseButtonState.Pressed*/)
			{
				CharPosition temp = _textLayer.GetChartPosition(location);

				if (temp != null)
				{
					if (_isLastMouseDownDouble)
					{
						Tuple<CharPosition, CharPosition> startSelection = GetWordSelection(_lastMouseDownPosition);
						Tuple<CharPosition, CharPosition> endSelection = GetWordSelection(temp);

						if (temp < _lastMouseDownPosition)
						{
							SetPosition(startSelection.Item2, endSelection.Item1, true);
						}
						else
						{
							SetPosition(startSelection.Item1, endSelection.Item2, true);

						}
					}
					else
					{
						SetPosition(null, temp, true);
					}
				}
			}

//			Cursor = IsCollapsePlace(location) ? Cursors.Hand : Cursors.IBeam;

			e.Handled = true;
		}

		private Tuple<CharPosition, CharPosition> GetWordSelection(CharPosition middleOfWord)
		{
			string line = _document.GetLineText(middleOfWord.Row);
			int start = Math.Max(line.LastIndexOfAny(UfeTextBoxInner.CtrlSplitChars, Math.Min(middleOfWord.Col, line.Length - 1)) + 1, 0);
			int end = line.IndexOfAny(UfeTextBoxInner.CtrlSplitChars, middleOfWord.Col);

			if (end < 0)
				end = line.Length;

			return new Tuple<CharPosition, CharPosition>(new CharPosition(start, middleOfWord.Row), new CharPosition(end, middleOfWord.Row));
		}

		private void HideHint()
		{
//			_popup.IsOpen = false;
		}

		public void AddPositionChangedEvent(CharPosition startSelection, CharPosition endSelection, CharPosition oldStartSelection, CharPosition oldEndSelection)
		{
			SingleActionPair pair = new SingleActionPair(
						new UfeTextBoxInner.SetPositionAction(startSelection, endSelection),
						new UfeTextBoxInner.SetPositionAction(oldStartSelection, oldEndSelection));
			_actionStackExplorer.DoAction(pair);
		}
	}
}
